<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Haozing\FastCore\Model;

use Exception;
use Haozing\FastCore\Constants\ErrorCode;
use Hyperf\Collection\Collection;
use Hyperf\Context\Context;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\SoftDeletes;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Schema\Schema;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Model\Model as BaseModel;
use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface;
use Hyperf\Stringable\Str;
use Qbhy\HyperfAuth\Authenticatable;
use function Hyperf\Config\config;

abstract class Model extends BaseModel implements CacheableInterface,Authenticatable
{
    use Cacheable;
    use SoftDeletes;
    use ModelMacroTrait;
    use TenantModelTrait;
    use UserDataScopeModelTrait;

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        //需要在配置文件中开启
        if (config('fastCore.debug_backtrace_log', false)) {
            $json = debug_backtrace_log();
            //连同hash，批量存入数据库
            // 判断DebugBacktraceLog数据库是否存在
            if (!Schema::hasTable('DebugBacktraceLog')) {
                Schema::create('DebugBacktraceLog', function (Blueprint $table) {
                    $table->engine = 'Innodb';
                    $table->comment('DebugBacktraceLog');
                    $table->bigIncrements('id')->comment('主键');
                    $table->addColumn('string', 'hash', ['length' => 32, 'comment' => '哈希值']);
                    $table->addColumn('smallInteger', 'weight', ['length' => 30, 'comment' => '权重（调用栈层级）']);
                    $table->addColumn('string', 'function', ['length' => 255, 'comment' => '函数或方法名'])->nullable();
                    $table->addColumn('string', 'class', ['length' => 255, 'comment' => '类名'])->nullable();
                    $table->addColumn('string', 'type', ['length' => 20, 'comment' => '调用类型（->或::）'])->nullable();
                    $table->addColumn('string', 'file', ['length' => 255,'comment' => '文件名'])->nullable();
                    $table->addColumn('smallInteger', 'line', ['length' => 11,'comment' => '行号'])->nullable();
                    $table->addColumn('string', 'object', ['length' => 255,'comment' => '调用该方法的类'])->nullable();
                    $table->addColumn('string', 'args', ['length' => 255,'comment' => '参数列表'])->nullable();
                    $table->addColumn('timestamp', 'created_at', ['precision' => 0, 'comment' => '创建时间'])->nullable();
                });
            }
            Db::table('DebugBacktraceLog')->insert($json);
        }

        $this->registerTenant();
        //$this->registerDataScope();
    }
    /**
     * 隐藏的字段列表
     * @var string[]
     */
    protected array $hidden = ['deleted_at'];

    /**
     * 数据权限创建者字段，表中需要有此字段
     */
    protected string $dataScopeCreatedField = 'created_by';

    /**
     * 数据权限修改者字段
     */
    protected string $dataScopeUpdatedField = 'updated_by';

    /**
     * 数据权限创建时间字段
     */
    protected string $dataScopeCreatedAtField = 'created_at';

    protected string $dataScopeUpdatedAtField = 'updated_at';

    /**
     * 数据权限部门id
     */
    protected string $dataScopeDeptIdField = 'dept_id';

    /**
     * 状态
     */
    public const ENABLE = 1;
    public const DISABLE = 2;

    /**
     * 默认每页记录数
     */
    public const PAGE_SIZE = 15;


    /**
     * 查询单条 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @return Builder|\Hyperf\Database\Model\Model|\Hyperf\Database\Query\Builder|object|null
     */
    public function getOneByWhere(array $where, array $fields = ['*'], array $options = [])
    {
        return $this->optionWhere($where, $options)->first($fields);
    }
    /**
     * 查询单条 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @return Builder|\Hyperf\Database\Model\Model|\Hyperf\Database\Query\Builder|object|null
     */
    public function getOneByWhereTenant(array $where, array $fields = ['*'], array $options = [])
    {
        return $this->optionWhere($where, $options)->useTenant()->first($fields);
    }
    /**
     * 查询多条 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @param int $limit 条数
     * @return Collection|Builder[]|\Hyperf\Database\Model\Collection
     */
    public function getListByWhere(array $where, array $fields = ['*'] , array $options = [], int $limit = 1000)
    {
        return $this->optionWhere($where, $options)->limit($limit)->get($fields);
    }
    /**
     * 查询多条 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @param int $limit 条数
     * @return Collection|Builder[]|\Hyperf\Database\Model\Collection
     */
    public function getListByWhereTenant(array $where, array $fields = ['*'] , array $options = [], int $limit = 1000)
    {
        return $this->optionWhere($where, $options)->useTenant()->limit($limit)->get($fields);
    }
    /**
     * 查询列表 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @return mixed
     */
    public function getPageByWhere(array $where, array $fields = ['*'], array $options=[])
    {

        $model = $this->optionWhere($where, $options);


        ## 分页参数
        $perPage  = isset($options['pageSize']) ? (int) $options['pageSize'] : 15;
        $pageName = $options['pageName'] ?? 'pageNo';
        $page     = isset($options['pageNo']) ? (int) $options['pageNo'] : null;

        ## 分页
        return $model->paginate($perPage, $fields, $pageName, $page);
    }

    /**
     * 查询列表 - 根据条件
     * @param array $where 条件
     * @param array $fields 字段
     * @param array $options
     * @return mixed
     */
    public function getPageByWhereTenant(array $where, array $fields = ['*'], array $options=[])
    {

        $model = $this->optionWhere($where, $options);


        ## 分页参数
        $perPage  = isset($options['pageSize']) ? (int) $options['pageSize'] : 15;
        $pageName = $options['pageName'] ?? 'pageNo';
        $page     = isset($options['pageNo']) ? (int) $options['pageNo'] : null;

        ## 分页
        return $model->userTenant()->paginate($perPage, $fields, $pageName, $page);
    }
    /**
     * 添加单条
     * @param array $data 数据
     * @return int 自增ID
     */
    public function addOne(array $data): int
    {
        return self::query()->insertGetId($this->columnsFormat($data));
    }
    /**
     * 添加单条
     * @param array $data 数据
     * @return int 自增ID
     */
    public function addOneTenant(array $data): int
    {
        return self::query()->insertGetId($this->columnsFormatTenant($data));
    }
    /**
     * 添加多条
     * @param array $data 数据
     * @return bool 是否成功
     */
    public function addAll(array $data): bool
    {
        //字段过滤
        foreach ($data as $key => $value) {
            $data[$key] = $this->columnsFormatTenant($value);
        }
        return self::query()->insert($data);
    }
    /**
     * 添加多条
     * @param array $data 数据
     * @return bool 是否成功
     */
    public function addAllTenant(array $data): bool
    {
        //字段过滤
        foreach ($data as $key => $value) {
            $data[$key] = $this->columnsFormat($value);
        }
        return self::query()->insert($data);
    }
    /**
     * 更新单条 - 根据ID
     * @param array $data 数据
     * @return int 影响行数
     */
    public function updateOneById(int $id, array $data): int
    {
        return self::query()->where('id', $id)->update($this->columnsFormat($data));
    }

    /**
     * 更新单条 - 根据条件
     * @param array $where 条件
     * @param array $data 数据
     * @param array $options
     * @return int 影响行数
     * @throws Exception
     */
    public function updateByWhere(array $where, array $data,array $options=[]): int
    {
        //字段过滤
        $model = $this->optionWhere($where, $options);
        $data = $model->update($this->columnsFormat($data));
        if (!$data){
            throw new Exception("更新失败",ErrorCode::BAD_REQUEST);
        }
        return $data;
    }
    /**
     * 更新单条 - 根据条件
     * @param array $where 条件
     * @param array $data 数据
     * @param array $options
     * @return int 影响行数
     * @throws Exception
     */
    public function updateByWhereTenant(array $where, array $data,array $options=[]): int
    {
        //字段过滤
        $model = $this->optionWhere($where, $options)->useTenant();
        $data = $model->update($this->columnsFormat($data));
        if (!$data){
            throw new Exception("更新失败",ErrorCode::BAD_REQUEST);
        }
        return $data;
    }
    /**
     * 删除 - 根据条件
     * @param array $where 条件
     * @return int 影响行数
     */
    public function deleteByWhere(array $where,array $options =[]): int
    {
        $model = $this->optionWhere($where, $options);

        return $model->delete();
    }
    /**
     * 删除 - 根据条件
     * @param array $where 条件
     * @return int 影响行数
     */
    public function deleteByWhereTenant(array $where,array $options =[]): int
    {
        $model = $this->optionWhere($where, $options)->useTenant();

        return $model->delete();
    }
    /**
     * 批量修改 - case...then...根据ID.
     * @param array $values 修改数据(必须包含ID)
     * @param bool $transToSnake 是否key转snake
     * @param bool $isColumnFilter 是否过滤不存在于表内的字段数据
     * @return int 影响条数
     */
    public function batchUpdateByIds(array $values, bool $transToSnake = false, bool $isColumnFilter = false): int
    {
        ## ksort
        foreach ($values as &$value) {
            ksort($value);
            $transToSnake && $value = $this->columnsFormat($value, $transToSnake, $isColumnFilter);
        }

        //获取数据库配置
        $config           = config('database');

        $tablePrefix      = Db::connection()->getTablePrefix();
        $table            = $this->getTable();
        $primary          = $this->getKeyName();
        [$sql, $bindings] = $this->compileBatchUpdateByIds($tablePrefix . $table, $values, $primary);

        return Db::update($sql, $bindings);
    }

    /**
     * 多条数据Pluck - 根据条件
     * @param array $where 条件
     * @param string $field 查询字段
     * @param array $options
     */
    public function pluckByTenant(array $where, string $field = 'id',$options=[])
    {
        $model = $this->optionWhere($where, $options);
        return $model->useTenant()->pluck($field);
    }

    /**
     * 根据条件获取某个字段的数量,自动注入租户id
     */
    public function countByTenant(array $where, string $field = 'id', array $options = []): int
    {
        $model = $this->optionWhere($where, $options);
        return $model->useTenant()->count($field);
    }
    /**
     * Compile batch update Sql.
     * @param string $table ...
     * @param array $values ...
     * @param string $primary ...
     * @return array update sql,bindings
     */
    protected function compileBatchUpdateByIds(string $table, array $values, string $primary): array
    {
        if (! is_array(reset($values))) {
            $values = [$values];
        }

        // Take the first value as columns
        $columns  = array_keys(current($values));
        // values
        $bindings = [];

        $setStr = '';
        foreach ($columns as $column) {
            if ($column === $primary) {
                continue;
            }

            $setStr .= " `{$column}` = case `{$primary}` ";
            foreach ($values as $row) {
                $value      = $row[$column];
                $bindings[] = $value;

                $setStr .= " when '{$row[$primary]}' then ? ";
            }
            $setStr .= ' end,';
        }
        // Remove the last character
        $setStr = substr($setStr, 0, -1);

        $ids    = array_column($values, $primary);
        $idsStr = implode(',', $ids);

        $sql = "update {$table} set {$setStr} where {$primary} in ({$idsStr})";
        return [$sql, $bindings];
    }

    /**
     * @param array $where 查询条件
     * @param string[] $options 可选项 ['orderByRaw'=> 'id asc', 'skip' => 15, 'take' => 5]
     * @return Builder|\Hyperf\Database\Query\Builder
     */
    public function optionWhere(array $where, array $options = [])
    {
        /** @var Builder $model */
        $model = new static();

        if (! empty($where)) {
            foreach ($where as $k => $v) {
                //['id','in',2]和['id'=>2]

                ## 一维数组
                if (! is_array($v) && ! is_numeric($k)) {
                    $model = $model->where($k, $v);

                    continue;
                }
                if (!is_array($v) && is_numeric($k)) {
                    $boolean = $where[3] ?? 'and';
                    if (count($where) == 2){
                        $model = $model->where($where[0], '=', $where[1], $boolean);
                        break;
                    }
                    $where[1]    = mb_strtoupper($where[1]);

                    if (in_array($where[1], ['=', '!=', '<', '<=', '>', '>=', 'LIKE', 'NOT LIKE'])) {
                        $model = $model->where($where[0], $where[1], $where[2], $boolean);
                    } elseif ($where[1] == 'IN') {
                        $model = $model->whereIn($where[0], $where[2], $boolean);
                    } elseif ($where[1] == 'NOT IN') {
                        $model = $model->whereNotIn($where[0], $where[2], $boolean);
                    } elseif ($where[1] == 'RAW') {
                        $model = $model->whereRaw($where[0], $where[2], $boolean);
                    }
                    break;
                }
                ## 二维索引数组
                if (is_numeric($k)) {

                    $boolean = $v[3] ?? 'and';
                    if (count($v) == 2){
                        $model = $model->where($v[0], '=', $v[1], $boolean);
                        continue;
                    }
                    $v[1]    = mb_strtoupper($v[1]);

                    if (in_array($v[1], ['=', '!=', '<', '<=', '>', '>=', 'LIKE', 'NOT LIKE'])) {
                        $model = $model->where($v[0], $v[1], $v[2], $boolean);
                    } elseif ($v[1] == 'IN') {
                        $model = $model->whereIn($v[0], $v[2], $boolean);
                    } elseif ($v[1] == 'NOT IN') {
                        $model = $model->whereNotIn($v[0], $v[2], $boolean);
                    } elseif ($v[1] == 'RAW') {
                        $model = $model->whereRaw($v[0], $v[2], $boolean);
                    }
                } else {
                    ## 二维关联数组
                    $model = $model->whereIn($k, $v);
                }
            }
        }
        ## 排序
        isset($options['orderByRaw']) && $model = $model->orderByRaw($options['orderByRaw']);

        ## 限制集合
        isset($options['skip']) && $model = $model->skip($options['skip']);
        isset($options['take']) && $model = $model->take($options['take']);
        var_dump($model->toSql());
        return $model;
    }

    /**
     * columnsFormat
     * @param array $value
     * @param bool $isTransSnake
     * @param bool $isColumnFilter
     * @return array
     */
    protected function columnsFormat(array $value,  bool $isTransSnake = false, bool $isColumnFilter = false): array
    {
        //使用this->getField方法获取数据库字段.
        //根据获取的字段过滤$data中不存在于表内的字段数据
        $formatValue = [];
        $isColumnFilter && $tableColumns = array_flip($this->getField());
        foreach ($value as $field => $fieldValue) {
            ## 转snake
            $isTransSnake && $field = Str::snake($field);
            ## 过滤
            if ($isColumnFilter && ! isset($tableColumns[$field])) {
                continue;
            }
            $formatValue[$field] = $fieldValue;
        }
        return $formatValue;
    }

    /**
     * 过滤字段并且添加Tenant_id
     * @param array $value
     * @param bool $isTransSnake
     * @param bool $isColumnFilter
     * @return array
     */
    protected function columnsFormatTenant(array $value,  bool $isTransSnake = false, bool $isColumnFilter = false): array
    {
        //使用this->getField方法获取数据库字段.
        //根据获取的字段过滤$data中不存在于表内的字段数据
        $formatValue = [];
        $isColumnFilter && $tableColumns = array_flip($this->getField());
        foreach ($value as $field => $fieldValue) {
            ## 转snake
            $isTransSnake && $field = Str::snake($field);
            ## 过滤
            if ($isColumnFilter && ! isset($tableColumns[$field])) {
                continue;
            }
            $formatValue[$field] = $fieldValue;

        }

        //如果$tableColumns中存在tenant_id字段，并且$value中不存在tenant_id字段，则添加tenant_id字段
        if (isset($tableColumns['tenant_id']) && !isset($value['tenant_id'])) {
            $formatValue['tenant_id'] = Context::get('pp-tenant-code');
        }
        return $formatValue;
    }
    //过滤字段
    protected function dataPermissionFilterFields(array $field): array
    {
        $user = Context::get('user');
        if (empty($user)) {
            return $field;
        }
        //如果field是['*']，则使用$this->getField获取表字段
        if (in_array('*', $field)) {
            $field = $this->getField();
        }
        $noFields = [];
        if (!empty($user['dataPermissionTableField'])) {
            $connectionName = $this->getConnectionName();
            $tableName = $this->getTable();
            foreach ( $user['dataPermissionTableField'] as $key => $value) {
                if ($value['connectionName'] == $connectionName && $value['tableName'] == $tableName) {
                    $noFields[] = $value['noField'];
                }
            }
        }
        // 排除field中，存在noField的字段
        foreach ($field as $key => $value) {
            if (in_array($value, $noFields)) {
                unset($field[$key]);
            }
        }
        return $field;
    }
    /**
     * 设置主键的值
     * @param int | string $value
     */
    public function setPrimaryKeyValue(int|string $value): void
    {
        $this->{$this->primaryKey} = $value;
    }

    /**
     * @return string
     */
    public function getPrimaryKeyType(): string
    {
        return $this->keyType;
    }

    /**
     * @param array $models
     * @return CoreCollection
     */
    public function newCollection(array $models = []): CoreCollection
    {
        return new CoreCollection($models);
    }

    /**
     * @return string
     */
    public function getDataScopeCreatedField(): string
    {
        return $this->dataScopeCreatedField;
    }

    /**
     * @param string $name
     * @return Model
     */
    public function setDataScopeCreatedField(string $name): self
    {
        $this->dataScopeCreatedField = $name;
        return $this;
    }

    public function getDataScopeCreatedAtField(): string
    {
        return $this->dataScopeCreatedAtField;
    }

    public function setDataScopeCreatedAtField(string $name): self
    {
        $this->dataScopeCreatedAtField = $name;
        return $this;
    }

    public function getDataScopeUpdatedField(): string
    {
        return $this->dataScopeUpdatedField;
    }

    public function setDataScopeUpdatedField(string $name): self
    {
        $this->dataScopeUpdatedField = $name;
        return $this;
    }

    public function getDataScopeUpdatedAtField(): string
    {
        return $this->dataScopeUpdatedAtField;
    }

    public function setDataScopeUpdatedAtField(string $name): self
    {
        $this->dataScopeUpdatedAtField = $name;
        return $this;
    }

    //$dataScopeDeptIdField
    public function getDataScopeDeptIdField(): string
    {
        return $this->dataScopeDeptIdField;
    }
    public function setDataScopeDeptIdField(string $name): self
    {
        $this->dataScopeDeptIdField = $name;
        return $this;
    }
    public function getId()
    {
        return $this->getKey();
    }

    public static function retrieveById($key): ?Authenticatable
    {
        return self::query()->find($key);
    }

    /**
     * 定义一个获取字段的接口
     * @return array
     */
    abstract function getField(): array;
}
